using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;

namespace Stunt.Nutss
{
	abstract public class Dispatch
	{

		#region Variables

		/// <summary>Signaling channel</summary>
		protected ISignalingContext sig;

		/// <summary>Socket for non-blocking IO</summary>
		private Socket _socket;

		/// <summary>Socket for signaling channel events</summary>
		private Socket sigSocket;

		/// <summary>Kkey for signaling channel events</summary>
		//private SelectionKey sigKey;
		/// <summary>Selector for non-blocking IO</summary>
		//private Selector selector;
		/// <summary>Continuations indexed by signaling messages that they subscribe to</summary>
		private Dictionary<ISignalingMessage, IContinuation> continuations = new Dictionary<ISignalingMessage, IContinuation>();

		/// <summary>Continuations in order of when their timeouts expire</summary>
		private SortedList<long, List<IContinuation>> timers = new SortedList<long, List<IContinuation>>();

		/// <summary>Reverse map from continuations to their timeout expiry</summary>
		private Dictionary<IContinuation, List<IContinuation>> revTimers = new Dictionary<IContinuation, List<IContinuation>>();

		/// <summary>Reverse map from continuations to the signaling messages that they subscribe to</summary>
		private Dictionary<IContinuation, ISignalingMessage> revContinuations = new Dictionary<IContinuation, ISignalingMessage>();

		/// <summary>System time</summary>
		private long clock;

		#endregion

		#region AddTimer

		/// <summary>
		/// Registers a timeout for a continuation
		/// </summary>
		/// <param name="tm">time at which timeout expires in milliseconds since epoch</param>
		/// <param name="k">the continuation</param>
		private void AddTimer(long tm, IContinuation k)
		{
			List<IContinuation> l = null;

			if (!timers.TryGetValue(tm, out l))
			{
				l = new List<IContinuation>();
				timers.Add(tm, l);
			}
			l.Add(k);

			if (revTimers.ContainsKey(k)) revTimers[k] = l;

			else revTimers.Add(k, l);
		}

		#endregion

		#region AddContinuation

		/// <summary>
		/// Adds a new continuation to the dispatcher
		/// </summary>
		/// <param name="k">the continuation</param>
		/// <param name="p">the message creating the continuation, null of none</param>
		protected void AddContinuation(IContinuation k, ISignalingMessage p)
		{
			ISignalingMessage interest = k.Init(p);
			continuations.Add(interest, k);
			revContinuations.Add(k, interest);
			revTimers.Remove(k);
			AddTimer(0L, k);
			//selector.WakeUp();      // wakeup the event handler thread if it's blocked
		}

		#endregion

		#region Cancel

		/// <summary>
		/// Cancels a continuation
		/// </summary>
		/// <param name="k">the continuation</param>
		private void Cancel(IContinuation k)
		{
			k.Cancel(this);
			continuations.Remove(revContinuations[k]);
			revContinuations.Remove(k);
		}

		#endregion

		#region Constructor

		/// <summary>
		/// Creates a new dispatcher for a given signaling channel
		/// </summary>
		protected Dispatch(ISignalingContext context)
		{
			try
			{
                //_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
				sigSocket = context.RegisterChannel();
                //byte[] buffer;
                //sigSocket.BeginReceive(buffer, SocketFlags.None, null, null);
                //sigSocket.BeginReceive();
                //this.selector = Selector.open();
                //this.sigKey = context.RegisterChannel(selector);
			}

			catch (Exception e)
			{
				Debug.WriteLine("Dispatch" + e.Message);
			}

			finally
			{
				this.sig = context;
			}
		}

		//NOTE: 
		//ArrayList socketReadList = new ArrayList();
		#endregion

		#region Properties

		/// <summary>
		/// Returns the non-blocking socket event selector
		/// </summary>
		/*

		public Selector Selector
		{
			get
			{
				return selector;
			}
		}
		*/

		public Socket Socket
		{
			get
			{
				return _socket;
			}
		}

		#endregion

		#region Step

		private void Step(IContinuation k, object arg)
		{
			int tmp;

			while ((tmp = k.Step(arg, this)) == 0)
				arg = null;

			if (tmp < 0)
				Cancel(k);

			else
				AddTimer(clock + tmp, k);
		}

		#endregion

		#region Callback

		/// <summary>
		/// Allows the continuation to communicate with the dispatcher
		/// </summary>
		/// <param name="k">the continuation</param>
		/// <param name="o">the object being passed to the dispatcher</param>
		public abstract void Callback(IContinuation k, object o);

		#endregion

		#region HandleMessage

		/// <summary>
		/// Handles a signaling message not handled by any continuation
		/// </summary>
		/// <param name="msg"></param>
		public abstract void HandleMessage(ISignalingMessage msg);

		#endregion

		#region ThreadProcess

		virtual public void ThreadProcess()
		{
			//HACK: will fix I promise
			List<Socket> readList = new List<Socket>();
			readList.Add(this.sigSocket);
			long tm;
			int num, tmp;

			try
			{

				while (Thread.CurrentThread.IsAlive && timers.Count > 0)
				{

					//---- Process timeouts in batches
					while (timers.Count > 0 && timers.Keys[0] < (clock = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + 10))
					{

						while (timers.Count > 0 && (tm = timers.Keys[0]) < clock)
						{
							List<IContinuation> l = timers[tm];
							timers.Remove(tm);

							if (tm < 0)

								foreach (IContinuation k in l)
									Cancel(k);

							else

								foreach (IContinuation k in l)
									Step(k, null);
						}
					}
					tm = (timers.Count < 1) ? 1000 : timers.Keys[0] - (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
					List<Socket> readListClone = new List<Socket>(readList);

                    Console.WriteLine("Entering select with timeout " + tm);
                    if (readListClone.Count == 0)
                    {
                        Thread.Sleep((int)tm);
                    }
                    else if (tm <= 10)
					{
						System.Net.Sockets.Socket.Select(readListClone, null, null, 0);
					}

					else
					{
						System.Net.Sockets.Socket.Select(readListClone, null, null, (int)tm*1000);
					}
                    Console.WriteLine("Leaving select with readListClone.Count = " + readListClone.Count);

					if (readList.Count < 1)
						continue;
					clock = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);

					foreach (Socket readSocket in readListClone)
					{
						IContinuation k = null;
						object arg = null;
						object socketAttachment = SocketAttachments.Get(readSocket.Handle.ToInt32());

						if (readSocket == sigSocket)
						{
							// Activity on the signaling channel. It's some ProxyMessage
                            Console.WriteLine("Proxy message processing...");
							ISignalingMessage msg = sig.Receive(/*readSocket*/);
							Console.WriteLine(String.Format("received msg={0}", msg));

							if (continuations.TryGetValue(msg, out k))
							{
								arg = msg;
							}

							else
							{                        // not claimed by existing continuations
								HandleMessage(msg);
							}
						}

						else if (/*key.isValid() &&*/ socketAttachment is IContinuation)
						{
							// Activity on an arbbbitrary key. Some direct socket operation
							// finished. Make sure the continuation is still valid.
                            Console.WriteLine("Activity on arbitrary key...");
							k = (IContinuation)socketAttachment;
							arg = readSocket;

							if (revContinuations[k] == null)
							{
								k = null;
								readSocket.Shutdown(SocketShutdown.Both);
								//key.cancel();
							}
						}

						if (k != null)
						{
							revTimers[k].Remove(k);
							Step(k, arg);
						}
					}
					readList.Clear();

					//selector.selectedKeys().clear();
					// Reap empty timer batches as continuations got pulled out in the last pass
					while (timers.Count > 0 && timers[tm = timers.Keys[0]].Count == 0)
						timers.Remove(tm);
				}
				sig.Close();
			}

			catch (Exception e)
			{
				Debug.WriteLine("Dispatch - ThreadProcess" + e.Message);
			}

			// close any continuations waiting for inputs since the event handler is terminating
			foreach (List<IContinuation> l in timers.Values)

				foreach (IContinuation c in l)
					c.Cancel(this);
		}

		#endregion

	}
}
